package cn.thoughtworks.school.services;


import cn.thoughtworks.school.entities.*;
import cn.thoughtworks.school.entities.commands.CreateSurveyQuizCommand;
import cn.thoughtworks.school.entities.commands.UpdateSurveyQuizCommand;
import cn.thoughtworks.school.entities.dto.AssignmentQuizData;
import cn.thoughtworks.school.handlers.BusinessException;
import cn.thoughtworks.school.repositories.SurveyQuizChoiceRepository;
import cn.thoughtworks.school.repositories.SurveyQuizRepository;
import cn.thoughtworks.school.repositories.SurveyQuizSubmissionRepository;
import cn.thoughtworks.school.requestParams.YmlConvertQuiz;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;

import javax.persistence.criteria.Predicate;
import javax.transaction.Transactional;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Service
public class SurveyQuizService {

    @Autowired
    private QuizService quizService;
    @Autowired
    private UserCenterService userCenterService;
    @Autowired
    private SurveyQuizRepository surveyQuizRepository;
    @Autowired
    private SurveyQuizSubmissionRepository surveyQuizSubmissionRepository;
    @Autowired
    private Utils utils;
    @Autowired
    private TagService tagService;
    @Autowired
    private SurveyQuizChoiceRepository surveyQuizChoicesRepository;

    public Page getSurveyCodingQuizzesPageable(Pageable pageable, Long id, Long userId, String description, List<String> tags, User user) {
        Long currentOrganizationId = userCenterService.getUserCurrentOrganizationId(user.getId());
        List<Long> quizIds = getQuizIdsByTags(tags);
        Page<SurveyQuiz> homeworkQuizPage = getSurveyQuizPage(currentOrganizationId, pageable, id, userId, description, quizIds);

        return utils.format(homeworkQuizPage, pageable);
    }

    private Page<SurveyQuiz> getSurveyQuizPage(Long organizationId, Pageable pageable, Long id, Long userId, String description, List<Long> quizIds) {
        return surveyQuizRepository.findAll((Specification<SurveyQuiz>) (root, query, criteriaBuilder) -> {
            List<Predicate> predicates = quizService.getDefaultPredicates(organizationId, id, userId, description, quizIds, root, criteriaBuilder);
            return criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()]));
        }, pageable);
    }

    private List<Long> getQuizIdsByTags(List<String> tags) {
        List<SurveyQuiz> quizzes = surveyQuizRepository.findQuizzesByTags(tags);
        if (Objects.nonNull(tags) && quizzes.isEmpty()) {
            return Collections.singletonList(-1L);
        }
        return quizzes.stream().map(SurveyQuiz::getId).collect(Collectors.toList());
    }

    @Transactional
    public void createSurveyQuiz(CreateSurveyQuizCommand createSurveyQuizCommand, User user) {
        SurveyQuiz surveyQuiz = saveSurveyQuiz(createSurveyQuizCommand, user);
        saveSurveyQuizChoices(surveyQuiz, createSurveyQuizCommand.getChoices());
    }

    protected void saveSurveyQuizChoices(SurveyQuiz surveyQuiz, List<String> choices) {
        List<SurveyQuizChoices> surveyQuizChoices = surveyQuiz.formatChoices(choices);
        surveyQuiz.setChoices(surveyQuizChoices);
        surveyQuizRepository.save(surveyQuiz);
    }

    private SurveyQuiz saveSurveyQuiz(CreateSurveyQuizCommand createSurveyQuizCommand, User user) {
        Long currentOrganizationId = userCenterService.getUserCurrentOrganizationId(user.getId());
        Set<Tag> tags = utils.formatTags(createSurveyQuizCommand.getTags(), user.getId());
        return surveyQuizRepository.save(createSurveyQuizCommand.build(user.getId(), currentOrganizationId, tags));

    }

    @Transactional
    public void delete(Long quizId) throws BusinessException {
        SurveyQuiz surveyQuiz = findByIdOrThrowException(quizId);
        surveyQuiz.delete();
    }

    public SurveyQuiz findByIdOrThrowException(Long quizId) throws BusinessException {
        return surveyQuizRepository.findById(quizId)
            .orElseThrow(() -> new BusinessException("Current quiz is not exist."));
    }

    @Transactional
    public void updateSurveyQuiz(Long quizId, UpdateSurveyQuizCommand updateSurveyQuizCommand, User user) throws BusinessException {
        SurveyQuiz surveyQuiz = findByIdOrThrowException(quizId);
        Set<Tag> tags = utils.formatTags(updateSurveyQuizCommand.getTags(), user.getId());
        surveyQuiz = updateSurveyQuizCommand.update(surveyQuiz, tags);
        surveyQuizRepository.save(surveyQuiz);
    }

    public List getQuizzesByIds(String ids) {
        List<Long> quizIds = Stream.of(ids.split(",")).map(Long::parseLong).collect(Collectors.toList());
        List<SurveyQuiz> quizzes = surveyQuizRepository.findAllByIsAvailableIsTrueAndIdIn(quizIds);

        List<Map> users = userCenterService.getUsersByIds(utils.getMakerIds(quizzes));

        return quizzes.stream()
            .map(basicQuiz -> formatSurveyByUsers(basicQuiz, users))
            .collect(Collectors.toList());

    }

    private Map formatSurveyByUsers(SurveyQuiz surveyQuiz, List<Map> users) {
        Map data = new ObjectMapper().convertValue(surveyQuiz, Map.class);
        data.put("maker", utils.getQuizMaker(users, data));
        return data;
    }

    public List getSurveyQuizzesSubmission(Long studentId, Long assignmentId, String ids) {
        List<Long> idList = Arrays.stream(ids.split(","))
            .map(Long::parseLong)
            .collect(Collectors.toList());
        List<SurveyQuiz> quizzes = surveyQuizRepository.findAllByIsAvailableIsTrueAndIdIn(idList);
        List<SurveyQuizSubmission> submissions = surveyQuizSubmissionRepository.findByQuizIdInAndAssignmentIdAndUserIdOrderByIdDesc(idList, assignmentId, studentId);

        return quizzes.stream()
            .map(quiz -> merge(quiz, submissions))
            .collect(Collectors.toList());
    }

    private Map merge(SurveyQuiz quiz, List<SurveyQuizSubmission> submissions) {
        Map result = new ObjectMapper().convertValue(quiz, Map.class);
        Optional<SurveyQuizSubmission> optional = submissions.stream()
            .filter(surveyQuizSubmission -> surveyQuizSubmission.getQuizId().toString().equals(result.get("id").toString()))
            .findFirst();

        if (optional.isPresent()) {
            result.put("userAnswer", optional.get().getUserAnswer());
        } else {
            result.put("userAnswer", "");
        }
        return result;
    }

    public void submitAnswer(Long studentId, Long assignmentId, List<Map> data) {
        data.stream().forEach(item -> {
            Long quizId = Long.valueOf(item.get("quizId").toString());
            String userAnswer = item.get("userAnswer").toString();
            SurveyQuizSubmission surveyQuizSubmission = new SurveyQuizSubmission(assignmentId, quizId, studentId, userAnswer);
            surveyQuizSubmissionRepository.save(surveyQuizSubmission);
        });
    }

    public void save(SurveyQuiz surveyQuiz) {
        surveyQuizRepository.save(surveyQuiz);
    }

    protected void convertUpdateSurveyQuizzes(YmlConvertQuiz converter, User current) {
        Long organizationId = userCenterService.getUserCurrentOrganizationId(current.getId());
        List<Tag> tags = tagService.updateTags(converter, organizationId, current.getId());

        converter.filterSurveyQuizzes().forEach(quiz -> {
            List<String> tagsName = (List<String>) quiz.get("tags");
            List<Tag> quizTags = tagsName.stream().map(name -> tags.stream()
                .filter(tag -> tag.getName().equals(name)).findFirst().get())
                .collect(Collectors.toList());

            SurveyQuiz surveyQuiz = SurveyQuiz.build(quiz, current.getId(), organizationId, quizTags);
            save(surveyQuiz);

            List options = (List) quiz.get("options");
            saveSurveyQuizChoices(surveyQuiz, options);
        });
    }

    public List<AssignmentQuizData> copy(List<AssignmentQuizData> surveyQuizzesData) {
        List<Long> ids = surveyQuizzesData.stream().map(AssignmentQuizData::getQuizId).collect(Collectors.toList());
        List<SurveyQuiz> surveyQuizzes = surveyQuizRepository.findByIdIn(ids);

        surveyQuizzesData.forEach(quizData -> {
            Long creatorId = quizData.getCreatorId();
            SurveyQuiz quiz = surveyQuizzes.stream()
                .filter(item -> item.getId().equals(quizData.getQuizId()))
                .findFirst().get();
            quiz.setTags(quizService.copyTags(quizData.getOrganizationId(), quiz.getTags(), creatorId));
            List<SurveyQuizChoices> surveyQuizChoices = quiz.getChoices();
            quiz = SurveyQuiz.copy(quiz, creatorId, quizData.getOrganizationId());
            surveyQuizRepository.save(quiz);
            List<String> choices = surveyQuizChoices.stream().map(SurveyQuizChoices::getChoice)
                .collect(Collectors.toList());
            surveyQuizChoices = genQuizChoices(choices, quiz.getId());
            surveyQuizChoicesRepository.saveAll(surveyQuizChoices);
            quizData.setNewQuizId(quiz.getId());
        });
        return surveyQuizzesData;
    }

    private List<SurveyQuizChoices> genQuizChoices(List<String> choices, Long quizId) {
        List<SurveyQuizChoices> result = new ArrayList<>();

        int i = 0;
        for (String choice : choices) {
            if (Objects.equals("", choice.trim())) {
                continue;
            }
            SurveyQuizChoicesComplexPK complexPk = new SurveyQuizChoicesComplexPK(i, quizId);
            SurveyQuizChoices basicQuizChoice = new SurveyQuizChoices(complexPk, choice);
            result.add(basicQuizChoice);
            i++;
        }
        return result;
    }

}
